package org.gienah.testing.xml;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;

/**
 * @author Santiago L. Valdarrama
 * @since E0.2
 */
public abstract class DependentBeanDefinitionParser
		implements BeanDefinitionParser {

	private static final String ELEMENT_DEPENDENCY = "dependency";
	private static final String ELEMENT_CONTEXT = "context";
	private static final String ATTRIBUTE_CLASS = "class";
	private static final String ATTRIBUTE_PROPERTY = "property";
	private static final String ATTRIBUTE_BEAN = "bean";

	protected abstract Class<?> getBeanClass(Element element);

	public final BeanDefinition parse(final Element element, final ParserContext parserContext) {
		Class<?> beanClass = getBeanClass(element);
		BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(beanClass);
		builder.setSource(parserContext.extractSource(element));
		if (parserContext.isNested()) {
			builder.setSingleton(parserContext.getContainingBeanDefinition().isSingleton());
		}

		parse(element, builder);

		BeanDefinition definition = builder.getBeanDefinition();
		String className = element.getAttribute(ATTRIBUTE_CLASS);

		BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, className);

		if (!parserContext.isNested()) {
			BeanDefinitionReaderUtils.registerBeanDefinition(holder, parserContext.getRegistry());
		}

		BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
		parserContext.getReaderContext().fireComponentRegistered(componentDefinition);

		return definition;
	}

	protected void parse(final Element element, final BeanDefinitionBuilder builder) {
		builder.addConstructorArg(parseDependencies(element));
		builder.addConstructorArg(parseContextFields(element));
	}

	@SuppressWarnings("unchecked")
	private Map<String, DependencyDefinition> parseDependencies(final Element element) {
		Map<String, DependencyDefinition> dependencies = new HashMap<String, DependencyDefinition>();

		List elements = DomUtils.getChildElementsByTagName(element, ELEMENT_DEPENDENCY);
		if (elements != null && elements.size() > 0) {
			for (Object iterator : elements) {
				Element dependency = (Element) iterator;

				String property = dependency.getAttribute(ATTRIBUTE_PROPERTY);
				String bean = dependency.getAttribute(ATTRIBUTE_BEAN);

				if (!StringUtils.hasText(bean)) {
					bean = property;
				}

				dependencies.put(property, new DependencyDefinition(property, bean));
			}
		}

		return dependencies;
	}

	@SuppressWarnings("unchecked")
	private List<String> parseContextFields(final Element element) {
		List<String> contextFields = new ArrayList<String>();

		List elements = DomUtils.getChildElementsByTagName(element, ELEMENT_CONTEXT);
		if (elements != null && elements.size() > 0) {
			for (Object iterator : elements) {
				Element contextField = (Element) iterator;
				contextFields.add(contextField.getAttribute(ATTRIBUTE_PROPERTY));
			}
		}

		return contextFields;
	}

}
